/*
 * File:    DynamicThreadGroup.java
 * Created: 11-Jun-2007
 * 
 * by Kenji Hollis <kenji@bitgatesoftware.com>
 * Released under the Creative Commons License version 2.5
 */

package com.bitgate.util.threads;

import java.lang.ref.WeakReference;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;

import com.bitgate.util.threads.DynamicThread.DynamicThreadLoad;

import static com.bitgate.util.debug.Debug.*;

public class DynamicThreadGroup implements Runnable
{
	class DynamicThreadContainer
	{
		private final WeakReference<DynamicThread> weakDynamicThread;
		private final WeakReference<Thread> thread;
		
		public DynamicThreadContainer(DynamicThread dynamicThread, Thread thread)
		{
			this.weakDynamicThread = new WeakReference<DynamicThread>(dynamicThread);
			this.thread = new WeakReference<Thread>(thread);
		}
		
		public DynamicThread getDynamicThread()
		{
			return weakDynamicThread.get();
		}
		
		public Thread getThread()
		{
			return thread.get();
		}
	}
	
	private final AtomicInteger minThreads;
	private final AtomicInteger maxThreads;
	private final LinkedList<DynamicThreadContainer> runningThreads;
	private final DynamicThread dynamicThread;
	private final int sleepTime;
	private boolean runningState;
	
	public DynamicThreadGroup(int minThreads, int maxThreads, int sleepTime, DynamicThread dynamicThread)
	{
		if (isDebugEnabled()) {
			debug("Starting Dynamic Thread Group: min=" + minThreads + " max=" + maxThreads + " sleep=" + sleepTime);
		}
		
		this.minThreads = new AtomicInteger(minThreads);
		this.maxThreads = new AtomicInteger(maxThreads);
		this.runningThreads = new LinkedList<DynamicThreadContainer>();
		this.dynamicThread = dynamicThread;
		this.sleepTime = sleepTime;
		this.runningState = true;
		
		initializeThreadGroup();
	}
	
	public void run()
	{
		while(runningState) {
			boolean isShrinkable = true;
			boolean isGrowable = true;
			
			for(DynamicThreadContainer dThread : runningThreads) {
				if (dThread.getDynamicThread().getBusyState() != DynamicThreadLoad.HIGH) {
					isGrowable = false;
				}
				
				if (dThread.getDynamicThread().getBusyState() != DynamicThreadLoad.IDLE) {
					isShrinkable = false;
				}
			}
			
			if (runningThreads.size() <= maxThreads.get() && isGrowable) {
				if (isDebugEnabled()) {
					debug("System under heavy load.  Starting new thread.  Size=" + runningThreads.size());
				}
				
				growThreadGroup();
			}
			
			if (runningThreads.size() > minThreads.get() && isShrinkable) {
				if (isDebugEnabled()) {
					debug("System load idle, shortening thread size.  Size=" + runningThreads.size());
				}
				
				shrinkThreadGroup();
			}
			
			try {
				synchronized(this) {
					wait(sleepTime * 1000L);
				}
			} catch(InterruptedException e) {
				if (isDebugEnabled()) {
					debug("Unable to sleep; thread interrupted.  Ending thread.");
				}
				
				break;
			}
		}
		
		shutdownAllThreads();
	}
	
	public void shutdown()
	{
		runningState = false;
	}
	
	public int numThreads()
	{
		return runningThreads.size();
	}
	
	public DynamicThread getAvailableThread()
	{
		DynamicThread lastNormalThread = null;
		
		// Find IDLE thread first.  Store the first normal load thread available.
		for(DynamicThreadContainer dtContainer : runningThreads) {
			DynamicThread dThread = dtContainer.getDynamicThread();
			
			if (dThread.getBusyState() == DynamicThreadLoad.IDLE) {
				return dThread;
			} else if (lastNormalThread == null && dThread.getBusyState() == DynamicThreadLoad.NORMAL) {
				lastNormalThread = dThread;
			}
		}
		
		return lastNormalThread;
	}
	
	private void shutdownAllThreads()
	{
		if (isDebugEnabled()) {
			debug("Shutting down " + runningThreads.size() + " thread(s).");
		}
		
		for(DynamicThreadContainer dThread : runningThreads) {
			dThread.getDynamicThread().shutdown();
		}
	}
	
	private void growThreadGroup()
	{
		DynamicThread dThread = dynamicThread.newInstance();
		Thread thread = null;
		
		thread = new Thread(dThread, "Dynamic Thread #" + (runningThreads.size() + 1));
		runningThreads.add(new DynamicThreadContainer(dThread, thread));
		
		thread.start();
	}
	
	private void shrinkThreadGroup()
	{
		DynamicThreadContainer dtContainer = runningThreads.removeLast();
		
		if (dtContainer.getDynamicThread().isRunning()) {
			dtContainer.getDynamicThread().shutdown();
		}
	}
	
	private void initializeThreadGroup()
	{
		for(int i = 0; i < minThreads.get(); i++) {
			growThreadGroup();
		}
		
		if (isDebugEnabled()) {
			debug("Threads initialized.  Pool size currently set to '" + runningThreads.size() + "'");
		}
	}
}